// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: jing

#pragma once

#include <boost/algorithm/string.hpp>
#include <map>
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <tuple>

#include "coin2/base/time.h"
#include "coin2/exchange/base/executor_util/market_ts.h"

namespace coin2::exchange::base::feed {

using boost::posix_time::ptime;
using boost::posix_time::time_duration;

class RequestGenerator {
 public:
  virtual ~RequestGenerator() = default;

  virtual std::vector<std::string> GenerateRequests(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) {
    // {channel} and {json_list}
    std::vector<std::string> reqs;

    // {num products} * {num channel types}
    if (request_template.find("{json_list}") != std::string::npos) {
      for (const auto& symbol : symbols) {
        nlohmann::json j;
        for (const auto& channel_name : channels) {
          std::string c = channel_templates.at(channel_name);
          boost::replace_all(c, "{}", symbol);
          j.push_back(c);
        }
        if (j.empty()) continue;
        std::string s = request_template;
        std::string json_list = j.dump();
        boost::replace_all(s, "{json_list}", json_list);
        reqs.push_back(s);
      }
    } else {
      for (const auto& channel_name : channels) {
        for (const auto& symbol : symbols) {
          // futures/depth5:{} => futures/depth5:BTC
          std::string c = channel_templates.at(channel_name);
          boost::replace_all(c, "{}", symbol);
          // {"sub": "{channel}", "id": "{channel}"} => ..
          std::string s = request_template;
          boost::replace_all(s, "{channel}", c);
          reqs.push_back(s);
        }
      }
    }
    return reqs;
  }

  virtual std::vector<std::pair<std::string, std::string>> GenerateHeader(
      const std::vector<std::string>& channels,
      const std::vector<std::string>& symbols,
      const std::map<std::string, std::string>& channel_templates,
      const std::string& request_template) {
    return {};
  }

  virtual std::vector<std::pair<std::string, std::string>>  GenerateHeader(
      const std::string& key_filepath) {
    THROW() << "Not implemented!";
    return {};
  }


  virtual std::string GenerateAuthRequest(const std::string& key_filepath) {
    THROW() << "Not implemented!";
    return std::string();
  }

  virtual std::string RegeneratePath(const std::string& orig_path,
                                     const std::string& key_filepath) {
    THROW() << "Not implemented!";
    return std::string();
  }

  static std::vector<std::pair<int64_t, int64_t>> GetDurations(
      const std::string& length,
      const std::string& start,
      const std::string& end,
      int64_t interval,
      int max_length) {
    // length default is 1200, start/end may be empty
    int64_t start_time, end_time;
    if (!start.empty()) {
      start_time = TimestampFromIso8601(start);
      if (!end.empty()) {
        // the last kline is supposed to unfreezed, query data cross end
        end_time = TimestampFromIso8601(end) + interval;
        end_time = std::min(end_time, MarketTimestampProvider::GetTimestamp());
      } else {
        end_time = MarketTimestampProvider::GetTimestamp();
      }
    } else {
      end_time = MarketTimestampProvider::GetTimestamp();
      start_time = end_time - (interval * stoll(length));
    }

    // let duraitons overlap in case of missing data
    std::vector<std::pair<int64_t, int64_t>> res;
    int64_t time_length = end_time - start_time;
    int64_t max_time_duration = (max_length - 2) * interval;
    int64_t n_length = time_length / max_time_duration;
    int64_t last_duration = time_length % max_time_duration;
    for (int64_t idx = 0; idx < n_length; ++idx) {
      auto begin = start_time + (idx * max_time_duration) - interval;
      auto last = begin + max_time_duration + interval;
      res.push_back(std::make_pair(begin, last));
    }

    if (last_duration > 0) {
      auto last = end_time;
      auto begin = last - last_duration - interval;
      res.push_back(std::make_pair(begin, last));
    }

    return res;
}

  static ptime TimestampRoundHour(int64_t timestamp) {
    ptime p = PtimeFromTimestamp(timestamp);
    time_duration td = p.time_of_day();
    time_duration round_td(td.hours(), 0, 0, 0);
    return ptime(p.date(), round_td);
  }

  static bool CheckLongQuery(
      const boost::posix_time::ptime& round_time,
      const std::string& symbol,
      const std::string& channel,
      std::map<std::tuple<std::string, std::string>,
               boost::posix_time::ptime>* last_long_query_time) {
    auto& last_long = (*last_long_query_time)[std::make_tuple(symbol, channel)];
    bool long_query = false;
    if (last_long.is_not_a_date_time() || round_time > last_long) {
      last_long = round_time;
      long_query = true;
    }

    return long_query;
  }

 protected:
  // every 1 hour send long query, used for REST api
  // REST query has no connection, all requests are genereated by <symbol,channel> only
  // for 1 <symbol,channel>, there is only 1 request for different <group, worker>
  std::map<std::tuple<std::string, std::string>, boost::posix_time::ptime> last_long_query_time_;
};

}  // namespace coin2::exchange::base::feed
